/**************************************************************************//**
*
* @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date            Author           Notes
* 2020-11-11      Wayne            First version
*
******************************************************************************/
#include <rtconfig.h>

#if defined(BSP_USING_SPI)

#define LOG_TAG                 "drv.spi"
#define DBG_ENABLE
#define DBG_SECTION_NAME        LOG_TAG
#define DBG_LEVEL               DBG_INFO
#define DBG_COLOR
#include <rtdbg.h>

#include <rthw.h>
#include <rtdevice.h>
#include <rtdef.h>

#include <drv_spi.h>
#include <nuc970_mfp.h>


/* Private define ---------------------------------------------------------------*/

enum
{
    SPI_START = -1,
#if defined(BSP_USING_SPI0)
    SPI0_IDX,
#endif
#if defined(BSP_USING_SPI1)
    SPI1_IDX,
#endif
    SPI_CNT
};

/* Private typedef --------------------------------------------------------------*/

/* Private functions ------------------------------------------------------------*/
static rt_uint32_t nu_spi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message);
static rt_err_t nu_spi_bus_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration);

/* Public functions -------------------------------------------------------------*/
void nu_spi_transfer(struct nu_spi *spi_bus, uint8_t *tx, uint8_t *rx, int length, uint8_t bytes_per_word);

static struct nu_spi nu_spi_arr [] =
{
#if defined(BSP_USING_SPI0)
    {
        .name = "spi0",
        .spi_base = SPI0,
    },
#endif
#if defined(BSP_USING_SPI1)
    {
        .name = "spi1",
        .spi_base = SPI1,
    },
#endif
}; /* spi nu_spi */

static rt_err_t nu_spi_bus_configure(struct rt_spi_device *device,
                                     struct rt_spi_configuration *configuration)
{
    struct nu_spi *spi_bus;
    uint32_t u32SPIMode;
    rt_err_t ret = RT_EOK;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    spi_bus = (struct nu_spi *) device->bus;

    /* Check mode */
    switch (configuration->mode & RT_SPI_MODE_3)
    {
    case RT_SPI_MODE_0:
        u32SPIMode = SPI_MODE_0;
        break;
    case RT_SPI_MODE_1:
        u32SPIMode = SPI_MODE_1;
        break;
    case RT_SPI_MODE_2:
        u32SPIMode = SPI_MODE_2;
        break;
    case RT_SPI_MODE_3:
        u32SPIMode = SPI_MODE_3;
        break;
    default:
        ret = -RT_EIO;
        goto exit_nu_spi_bus_configure;
    }

    /* Check data width */
    if (!(configuration->data_width == 8  ||
            configuration->data_width == 16 ||
            configuration->data_width == 24 ||
            configuration->data_width == 32))
    {
        ret = -RT_EINVAL;
        goto exit_nu_spi_bus_configure;
    }

    rt_memcpy(&spi_bus->configuration, configuration, sizeof(*configuration));

    SPI_Open(spi_bus->spi_base, configuration->max_hz, configuration->data_width, u32SPIMode);

    if (configuration->mode & RT_SPI_CS_HIGH)
    {
        SPI_SET_SS_LVL_HIGH(spi_bus->spi_base);
    }
    else
    {
        SPI_SET_SS_LVL_LOW(spi_bus->spi_base);
    }
    SPI_SET_SS_DESABLE(spi_bus->spi_base, 1);

    if (configuration->mode & RT_SPI_MSB)
    {
        /* Set sequence to MSB first */
        SPI_SET_MSB_FIRST(spi_bus->spi_base);
    }
    else
    {
        /* Set sequence to LSB first */
        SPI_SET_LSB_FIRST(spi_bus->spi_base);
    }

exit_nu_spi_bus_configure:

    return ret;
}

static int nu_spi_read(SPI_TypeDef *spi_base, rt_uint8_t *recv_addr, rt_uint8_t bytes_per_word)
{
    rt_uint32_t val;

    // Read RX data
    val = SPI_Read(spi_base, 0);
    switch (bytes_per_word){
    case 4:
        nu_set32_le(recv_addr, val);
    break;
    case 3:
        nu_set24_le(recv_addr, val);
    break;
    case 2:
        nu_set16_le(recv_addr, val);
    break;
    case 1:
        *recv_addr = (rt_uint8_t)val;
    break;
    default:
    break;
    }

    return bytes_per_word;
}

static int nu_spi_write(SPI_TypeDef *spi_base, const uint8_t *send_addr, uint8_t bytes_per_word)
{
    rt_uint32_t data;
    // Input data to SPI TX
    switch (bytes_per_word) {
    case 4:
        data = nu_get32_le(send_addr);
    break;
    case 3:
        data = nu_get24_le(send_addr);
    break;
    case 2:
        data = nu_get16_le(send_addr);
    break;
    case 1:
        data = *((uint8_t *)send_addr);
    break;
    default:
    break;
    }

    SPI_Write(spi_base, 0, data);
    SPI_Trigger(spi_base);
    while(SPI_isBusy(spi_base) == TRUE);

    return bytes_per_word;
}

/**
 * @brief SPI bus polling
 * @param dev : The pointer of the specified SPI module.
 * @param send_addr : Source address
 * @param recv_addr : Destination address
 * @param length    : Data length
 */
static void nu_spi_transfer(struct nu_spi *spi_bus,
        uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word)
{
    SPI_TypeDef *spi_base = spi_bus->spi_base;

    // Write-only
    if ((send_addr != RT_NULL) && (recv_addr == RT_NULL))
    {
        while (length > 0)
        {
            send_addr += nu_spi_write(spi_base, send_addr, bytes_per_word);
            length -= bytes_per_word;
        }
    }
    // Read-only
    else if ((send_addr == RT_NULL) && (recv_addr != RT_NULL))
    {
        spi_bus->dummy = 0;
        while (length > 0)
        {
            /* Input data to SPI TX FIFO */
            length -= nu_spi_write(spi_base, (const uint8_t *)&spi_bus->dummy, bytes_per_word);

            /* Read data from RX FIFO */
            recv_addr += nu_spi_read(spi_base, recv_addr, bytes_per_word);
        }
    }
    // Read&Write
    else
    {
        while (length > 0)
        {
            /* Input data to SPI TX FIFO */
            send_addr += nu_spi_write(spi_base, send_addr, bytes_per_word);
            length -= bytes_per_word;

            /* Read data from RX FIFO */
            recv_addr += nu_spi_read(spi_base, recv_addr, bytes_per_word);
        }
    }
}

static rt_uint32_t nu_spi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct nu_spi *spi_bus;
    struct rt_spi_configuration *configuration;
    uint8_t bytes_per_word;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);
    RT_ASSERT(message != RT_NULL);

    spi_bus = (struct nu_spi *) device->bus;
    configuration = (struct rt_spi_configuration *)&spi_bus->configuration;
    bytes_per_word = configuration->data_width / 8;

    if ((message->length % bytes_per_word) != 0)
    {
        /* Say bye. */
        LOG_E("%s: error payload length(%d%%%d != 0).\n", spi_bus->name, message->length, bytes_per_word);
        return 0;
    }

    if (message->length > 0)
    {
        if (message->cs_take && !(configuration->mode & RT_SPI_NO_CS))
        {
            SPI_SET_SS_ENABLE(spi_bus->spi_base, 1);
        }

        nu_spi_transfer(spi_bus, (uint8_t *)message->send_buf, (uint8_t *)message->recv_buf, message->length, bytes_per_word);

        if (message->cs_release && !(configuration->mode & RT_SPI_NO_CS))
        {
            SPI_SET_SS_DESABLE(spi_bus->spi_base, 1);
        }

    }

    return message->length;
}

/* Private variables ------------------------------------------------------------*/
static struct rt_spi_ops nu_spi_poll_ops =
{
    .configure = nu_spi_bus_configure,
    .xfer      = nu_spi_bus_xfer,
};

/**
 * Hardware SPI Initial
 */
static int rt_hw_spi_init(void)
{
    int i;

    for (i = (SPI_START + 1); i < SPI_CNT; i++)
    {
        MFP_SPIInit(nu_spi_arr[i].spi_base);
        rt_spi_bus_register(&nu_spi_arr[i].dev, nu_spi_arr[i].name, &nu_spi_poll_ops);
    }

    return 0;
}
INIT_DEVICE_EXPORT(rt_hw_spi_init);

rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, rt_uint32_t cs_pin)
{
    struct rt_spi_device *spi_device = RT_NULL;
    rt_err_t result = RT_EOK;

    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);

    spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
    if (spi_device == RT_NULL)
    {
        LOG_E("no memory, spi bus attach device failed!\n");
        result = -RT_ENOMEM;
        goto __exit;
    }

    result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, RT_NULL);

__exit:
    if (result != RT_EOK)
    {
        if (spi_device)
        {
            rt_free(spi_device);
        }
    } else {
        struct rt_spi_configuration cfg;
        cfg.data_width = 8;
        cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible Modes 0 */
        cfg.max_hz = 2 * 1000 * 1000; /* SPI Interface with Clock Speeds Up to 2 MHz */
        spi_device->config = cfg;
    }

    return  result;
}

#endif //#if defined(BSP_USING_SPI)
